package algebra.rewriting.kfree;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import option.KBVocabulary;
import tableaux.rules.RuleLazyUnfolding;

import kb.DescriptionLogicsKB;

import algebra.query.pd.*;

import entities.concept.AndConcept;
import entities.concept.Concept;
import entities.concept.DomainConcept;
import entities.concept.DomainConceptEQ;
import entities.concept.ExistsConcept;
import entities.role.Role;

public class LangPd {

	private static Map<ProjDesc, Set<Concept>> cachedLpds = new HashMap<ProjDesc, Set<Concept>>();
	public static boolean useStarToReplaceConstants = true;

	public static void resetCache() {
		cachedLpds.clear();
	}

	/*
	 * Test if pd1 is more general than pd2, i.e., Lpd1 subsumes Lpd2
	 */
	public static boolean moreGeneral(ProjDesc pd1,
			ProjDesc pd2) {
		Set<Concept> tup1 = getLangPdTup(pd1, new HashSet<String>(), null);
		Set<Concept> tup2 = getLangPdTup(pd2, new HashSet<String>(), null);
		return tup1.containsAll(tup2);
	}
	
//	private boolean satSet (Set<Concept> sub){
//		Map<Role, Set<String>> fvs = new HashMap<Role, Set<String>>();
//		for(Concept c:sub){
//			if(c instanceof DomainConceptEQ){
//				DomainConceptEQ<?, ?> ec = (DomainConceptEQ<?, ?>) c;
//				Role f = (Role) ec.getRealFeature();
//				String v = (String) ec.getRealValue();
//				if(fvs.containsKey(f) && !fvs.get(f).contains(v)){
//					return false;
//				}
//				Set<String> vs = null;
//				if(fvs.containsKey(f)){
//					vs = fvs.get(f);
//				}else{
//					vs = new HashSet<String> ();
//				}
//				vs.add(v);
//			}
//		}
//		return true;
//	}

	public static Set<Concept> getLangPd(ProjDesc pd,
			Set<String> ks, DescriptionLogicsKB kb) {
		Set<Concept> tup = getLangPdTup(pd, ks, kb);
//		System.err.println("tuple size: "+tup.size()+" for "+pd);
		if(pd.hasConjunctiveFeature()){
			return tup;
		}
		// generate subset cons
		Set<Concept> ret = new HashSet<Concept>();
		for (Set<Concept> sub : powerSet(tup)) {
			ret.add(Concept.getConjunctiveConcept(sub));
		}
		// System.err.println("power set size:"+i+"; original set:"+tup.size());
		return ret;
	}

	public static Set<Concept> getLangPdTup(ProjDesc pd,
			Set<String> ks, DescriptionLogicsKB kb) {
		if (cachedLpds.containsKey(pd)) {
			return cachedLpds.get(pd);
		}
		Set<Concept> cons = new HashSet<Concept>();
		if(pd instanceof PdConcept){
			cons.add(((PdConcept) pd).getConcept());
			cons.add(KBVocabulary.CONCEPT_TOP);
		}else if(pd instanceof PdFeature){
			DomainConcept<Role, String> fstar = new DomainConceptEQ<Role, String>(
					((PdFeature) pd).getFeature(), KBVocabulary.AnyStringInKB);
			cons.add(fstar);
			if (!useStarToReplaceConstants) {
				Role r = ((PdFeature) pd).getFeature();
				for (String k : ks) {
					cons.add(new DomainConceptEQ<Role, String>(r, k));
				}
			} else if (kb != null) {
				// add axioms f=* EQs f=1 etc.
				Role r = ((PdFeature) pd).getFeature();
				RuleLazyUnfolding.addTfFeature(r);
				RuleLazyUnfolding.addTfValues(ks);
			}
			cons.add(KBVocabulary.CONCEPT_TOP);
		}else if(pd instanceof PdConjunction){
			for (Concept c : getLangPdTup(((PdConjunction) pd).getSubPd1(), ks, kb)) {
				for (Concept d : getLangPdTup(((PdConjunction) pd).getSubPd2(), ks, kb)) {
					Set<Concept> ac = new HashSet<Concept>();
					if(!c.isTop()){
						ac.add(c);
					}
					if(!d.isTop()){
						ac.add(d);
					}
					cons.add(Concept.getConjunctiveConcept(ac));
				}
			}
		}else if(pd instanceof PdRole){
			ProjDesc pc = ((PdRole) pd).getSubPd();
			Role r = ((PdRole) pd).getRole();
			for (Concept c : getLangPd(pc, ks, kb)) {
				cons.add(new ExistsConcept(r, c));
			}
		}
		cachedLpds.put(pd, cons);
		return cons;
	}

	public static <T> Set<Set<T>> powerSet(Set<T> originalSet) {
		Set<Set<T>> sets = new HashSet<Set<T>>();
		if (originalSet.isEmpty()) {
			sets.add(new HashSet<T>());
			return sets;
		}
		Set<T> sub = new HashSet<T>(originalSet);
		T head = sub.iterator().next();
		sub.remove(head);
		Set<T> rest = new HashSet<T>(sub);
		for (Set<T> set : powerSet(rest)) {
			Set<T> newSet = new HashSet<T>();
			newSet.add(head);
			newSet.addAll(set);
			sets.add(newSet);
			sets.add(set);
		}
		return sets;
	}

}
